# 1. 依赖库导入（新增logging用于日志记录）
import numpy as np
import math
import pygad
import matplotlib.pyplot as plt
import pandas as pd
from openpyxl import Workbook
import logging
from datetime import datetime


# 2. 日志配置（生成持久化日志文件）
def init_logger():
    logger = logging.getLogger("FY1_3smoke_optimization")
    logger.setLevel(logging.INFO)

    # 避免重复添加处理器
    if not logger.handlers:
        # 日志格式：时间戳 + 日志级别 + 内容
        formatter = logging.Formatter(
            "%(asctime)s - %(levelname)s - %(message)s",
            datefmt="%Y-%m-%d %H:%M:%S"
        )

        # 1. 控制台处理器（命令行输出）
        console_handler = logging.StreamHandler()
        console_handler.setFormatter(formatter)
        logger.addHandler(console_handler)

        # 2. 文件处理器（日志文件输出）
        log_filename = f"FY1_3smoke_optimization_{datetime.now().strftime('%Y%m%d%H%M%S')}.log"
        file_handler = logging.FileHandler(log_filename, encoding="utf-8")
        file_handler.setFormatter(formatter)
        logger.addHandler(file_handler)

    return logger


# 初始化日志对象
logger = init_logger()

# 绘图参数配置（支持中文显示）
plt.rcParams['font.sans-serif'] = ['Microsoft YaHei']
plt.rcParams['axes.unicode_minus'] = False
plt.switch_backend('TkAgg')

# 3. 题目核心参数定义（严格源自A题.pdf）
### 3.1 目标参数（🔶3-6、🔶3-8）
target_fake = np.array([0, 0, 0])  # 假目标：原点
target_true_center = np.array([0, 200, 0])  # 真目标下底面圆心
target_true_radius = 7  # 真目标圆柱半径（m）
target_true_height = 10  # 真目标圆柱高度（m）

### 3.2 导弹M1参数（🔶3-6、🔶3-5）
missile_M1_init = np.array([20000, 0, 2000])  # M1初始位置
missile_speed = 300  # M1飞行速度（m/s）
missile_dist_to_fake = np.linalg.norm(target_fake - missile_M1_init)
missile_time_to_fake = missile_dist_to_fake / missile_speed  # ≈66.999s（核心约束）

### 3.3 无人机FY1参数（🔶3-6、🔶3-7）
drone_FY1_init = np.array([17800, 0, 1800])  # FY1初始位置
drone_speed_min = 70  # FY1最小速度（m/s）
drone_speed_max = 140  # FY1最大速度（m/s）
drone_flight_height = drone_FY1_init[2]  # FY1等高度飞行（z=1800m）

### 3.4 烟幕干扰弹参数（🔶3-5、🔶3-8）
smoke_radius = 10  # 烟幕有效半径（中心10m内）
smoke_sink_speed = 3  # 烟幕匀速下沉速度（m/s）
smoke_valid_duration = 20  # 烟幕有效时长（起爆后20s）
g = 9.8  # 重力加速度（m/s²）
smoke_drop_interval_min = 1  # 多弹投放最小间隔（≥1s）
smoke_min_explode_z = 50  # 起爆点最低z坐标（避免地面以下）


# 4. 核心工具函数（与A题约束一致，无修改）
def get_true_target_bounding_box():
    x_min = target_true_center[0] - target_true_radius
    x_max = target_true_center[0] + target_true_radius
    y_min = target_true_center[1] - target_true_radius
    y_max = target_true_center[1] + target_true_radius
    z_min = target_true_center[2]
    z_max = target_true_center[2] + target_true_height
    return [
        np.array([x_min, y_min, z_min]), np.array([x_max, y_min, z_min]),
        np.array([x_max, y_max, z_min]), np.array([x_min, y_max, z_min]),
        np.array([x_min, y_min, z_max]), np.array([x_max, y_min, z_max]),
        np.array([x_max, y_max, z_max]), np.array([x_min, y_max, z_max])
    ]


target_true_vertices = get_true_target_bounding_box()


def calculate_missile_real_time_pos(t):
    if t >= missile_time_to_fake - 1e-6:
        return target_fake
    flight_dir = target_fake - missile_M1_init
    flight_dir_unit = flight_dir / np.linalg.norm(flight_dir)
    displacement = missile_speed * t * flight_dir_unit
    return missile_M1_init + displacement


def calculate_smoke_drop_point(drone_speed, drone_heading, drop_delay):
    x = drone_FY1_init[0] + drone_speed * math.cos(drone_heading) * drop_delay
    y = drone_FY1_init[1] + drone_speed * math.sin(drone_heading) * drop_delay
    z = drone_flight_height
    return np.round(np.array([x, y, z]), 2)


def calculate_smoke_explosion_point(drone_speed, drone_heading, drop_delay, detonate_delay):
    drop_point = calculate_smoke_drop_point(drone_speed, drone_heading, drop_delay)
    max_fall_distance = drop_point[2] - smoke_min_explode_z
    if max_fall_distance < 0:
        detonate_delay = 0.1
        fall_distance = 0.5 * g * (detonate_delay ** 2)
    else:
        max_allowed_det = math.sqrt(2 * max_fall_distance / g)
        detonate_delay = min(detonate_delay, max_allowed_det)
        fall_distance = 0.5 * g * (detonate_delay ** 2)

    x = drop_point[0] + drone_speed * math.cos(drone_heading) * detonate_delay
    y = drop_point[1] + drone_speed * math.sin(drone_heading) * detonate_delay
    z = max(drop_point[2] - fall_distance, smoke_min_explode_z)
    return np.round(np.array([x, y, z]), 2)


def calculate_smoke_real_time_center(drone_speed, drone_heading, drop_delay, detonate_delay, t):
    detonate_time = drop_delay + detonate_delay
    if t < detonate_time - 1e-6:
        return np.array([0, 0, 0])
    explosion_point = calculate_smoke_explosion_point(drone_speed, drone_heading, drop_delay, detonate_delay)
    sink_time = t - detonate_time
    sink_distance = smoke_sink_speed * sink_time
    real_z = max(explosion_point[2] - sink_distance, 0.0)
    return np.array([explosion_point[0], explosion_point[1], real_z])


def is_smoke_effective(drone_speed, drone_heading, drop_delay, detonate_delay, t):
    missile_pos = calculate_missile_real_time_pos(t)
    smoke_center = calculate_smoke_real_time_center(drone_speed, drone_heading, drop_delay, detonate_delay, t)
    if np.all(smoke_center == np.array([0, 0, 0])):
        return False

    for vertex in target_true_vertices:
        vec_AC = smoke_center - missile_pos
        vec_AB = vertex - missile_pos
        ab_sq_len = np.dot(vec_AB, vec_AB)
        if ab_sq_len < 1e-12:
            continue

        k = np.dot(vec_AC, vec_AB) / ab_sq_len
        if k <= 0:
            dist = np.linalg.norm(vec_AC)
        elif k >= 1:
            dist = np.linalg.norm(smoke_center - vertex)
        else:
            cross_prod = np.cross(vec_AC, vec_AB)
            dist = np.linalg.norm(cross_prod) / math.sqrt(ab_sq_len)

        if dist <= smoke_radius + 1e-6:
            return True
    return False


def get_single_smoke_effective_intervals(drone_speed, drone_heading, drop_delay, detonate_delay, time_step=0.05):
    detonate_time = drop_delay + detonate_delay
    effective_start = detonate_time
    effective_end = min(detonate_time + smoke_valid_duration, missile_time_to_fake)
    if effective_start >= effective_end + 1e-6:
        return []

    intervals = []
    current_start = None
    t = effective_start
    while t <= effective_end + 1e-6:
        if is_smoke_effective(drone_speed, drone_heading, drop_delay, detonate_delay, t):
            if current_start is None:
                current_start = t
        else:
            if current_start is not None:
                intervals.append((round(current_start, 2), round(t, 2)))
                current_start = None
        t += time_step

    if current_start is not None:
        intervals.append((round(current_start, 2), round(effective_end, 2)))
    return [(s, e) for s, e in intervals if (e - s) > 0.1]


def merge_smoke_intervals(all_intervals):
    flat_intervals = []
    for intervals in all_intervals:
        flat_intervals.extend(intervals)
    if not flat_intervals:
        return []
    sorted_intervals = sorted(flat_intervals, key=lambda x: x[0])
    merged = [sorted_intervals[0]]
    for current in sorted_intervals[1:]:
        last = merged[-1]
        if current[0] <= last[1] + 0.1:
            merged[-1] = (last[0], max(last[1], current[1]))
        else:
            merged.append(current)
    return merged


def calculate_total_effective_duration(params, time_step=0.05):
    v, theta, t1, t2, t3, d1, d2, d3 = params
    t_list = sorted([t1, t2, t3])
    interval1 = get_single_smoke_effective_intervals(v, theta, t_list[0], d1, time_step)
    interval2 = get_single_smoke_effective_intervals(v, theta, t_list[1], d2, time_step)
    interval3 = get_single_smoke_effective_intervals(v, theta, t_list[2], d3, time_step)
    merged_intervals = merge_smoke_intervals([interval1, interval2, interval3])
    total_duration = sum([round(e - s, 2) for s, e in merged_intervals])
    return total_duration, merged_intervals, [interval1, interval2, interval3], t_list


# 5. 遗传算法优化（核心修改：迭代参数输出）
### 5.1 适应度函数（无修改，确保A题约束）
def fitness_function(ga_instance, solution, solution_idx):
    v, theta, t1, t2, t3, d1, d2, d3 = solution
    t_list = sorted([t1, t2, t3])

    # 约束1：速度范围
    if not (drone_speed_min - 1e-6 <= v <= drone_speed_max + 1e-6):
        return 0.0

    # 约束2：投放延迟非负且间隔≥1s
    if t_list[0] < -1e-6 or t_list[1] < -1e-6 or t_list[2] < -1e-6:
        return 0.0
    if (t_list[1] - t_list[0] < smoke_drop_interval_min - 1e-6 or
            t_list[2] - t_list[1] < smoke_drop_interval_min - 1e-6):
        return 0.0

    # 约束3：起爆延迟≥0.1s
    if d1 < 0.1 - 1e-6 or d2 < 0.1 - 1e-6 or d3 < 0.1 - 1e-6:
        return 0.0

    # 约束4：起爆时间≤导弹到达时间
    det_time1 = t_list[0] + d1
    det_time2 = t_list[1] + d2
    det_time3 = t_list[2] + d3
    if (det_time1 > missile_time_to_fake + 1e-6 or
            det_time2 > missile_time_to_fake + 1e-6 or
            det_time3 > missile_time_to_fake + 1e-6):
        return 0.0

    total_duration, _, _, _ = calculate_total_effective_duration([v, theta, t1, t2, t3, d1, d2, d3])
    return total_duration


### 5.2 迭代回调函数（核心修改：参数输出与日志记录）
generation_best_fitness = []


def on_generation_callback(ga_instance):
    # 1. 获取当前代最优个体与适应度
    current_gen = ga_instance.generations_completed
    current_fitness = np.max(ga_instance.last_generation_fitness)
    best_idx_in_gen = np.argmax(ga_instance.last_generation_fitness)
    best_sol_in_gen = ga_instance.population[best_idx_in_gen]
    generation_best_fitness.append(current_fitness)

    # 2. 解析最优个体参数（按A题约束整理）
    v = best_sol_in_gen[0]
    theta_rad = best_sol_in_gen[1]
    theta_deg = (theta_rad * 180 / math.pi) % 360  # 弧度转角度
    t1_raw, t2_raw, t3_raw = best_sol_in_gen[2], best_sol_in_gen[3], best_sol_in_gen[4]
    d1, d2, d3 = best_sol_in_gen[5], best_sol_in_gen[6], best_sol_in_gen[7]

    # 强制投放延迟有序（确保间隔约束）
    t_list = sorted([t1_raw, t2_raw, t3_raw])
    t1, t2, t3 = t_list[0], t_list[1], t_list[2]
    det1 = t1 + d1  # 第1枚弹起爆时间
    det2 = t2 + d2  # 第2枚弹起爆时间
    det3 = t3 + d3  # 第3枚弹起爆时间

    # 计算关键位置（投放点、起爆点，A题🔶3-8要求）
    drop1 = calculate_smoke_drop_point(v, theta_rad, t1)
    expl1 = calculate_smoke_explosion_point(v, theta_rad, t1, d1)
    drop2 = calculate_smoke_drop_point(v, theta_rad, t2)
    expl2 = calculate_smoke_explosion_point(v, theta_rad, t2, d2)
    drop3 = calculate_smoke_drop_point(v, theta_rad, t3)
    expl3 = calculate_smoke_explosion_point(v, theta_rad, t3, d3)

    # 计算有效时间段（A题🔶3-5要求）
    total_duration, merged_intervals, single_intervals, _ = calculate_total_effective_duration(best_sol_in_gen)
    fmt_interval = lambda intervals: ";".join([f"({s:.2f},{e:.2f})" for s, e in intervals]) if intervals else "无"
    interval1_str = fmt_interval(single_intervals[0])
    interval2_str = fmt_interval(single_intervals[1])
    interval3_str = fmt_interval(single_intervals[2])
    merged_interval_str = fmt_interval(merged_intervals)

    # 3. 验证约束满足情况（A题核心约束）
    speed_ok = f"是（{v:.2f}m/s）" if (drone_speed_min <= v <= drone_speed_max) else "否"
    interval1_ok = f"是（{t2 - t1:.2f}s）" if (t2 - t1 >= smoke_drop_interval_min) else "否"
    interval2_ok = f"是（{t3 - t2:.2f}s）" if (t3 - t2 >= smoke_drop_interval_min) else "否"
    det_time_ok = f"是（{det1:.2f}/{det2:.2f}/{det3:.2f}s ≤ {missile_time_to_fake:.2f}s）" if (
                det1 <= missile_time_to_fake and det2 <= missile_time_to_fake and det3 <= missile_time_to_fake) else "否"
    expl_z_ok = f"是（{expl1[2]}/{expl2[2]}/{expl3[2]}m ≥ {smoke_min_explode_z}m）" if (
                expl1[2] >= smoke_min_explode_z and expl2[2] >= smoke_min_explode_z and expl3[
            2] >= smoke_min_explode_z) else "否"

    # 4. 命令行输出（简洁核心参数）
    print(f"\n【第{current_gen:3d}代最优参数】")
    print(
        f"1. 总有效遮蔽时长：{total_duration:.2f}s | 约束满足：速度{speed_ok} | 间隔{interval1_ok}/{interval2_ok} | 起爆时间{det_time_ok} | 起爆高度{expl_z_ok}")
    print(f"2. 无人机参数：速度={v:.2f}m/s | 航向={theta_deg:.2f}°")
    print(f"3. 投放/起爆延迟（弹1/2/3）：({t1:.2f}/{d1:.2f})s | ({t2:.2f}/{d2:.2f})s | ({t3:.2f}/{d3:.2f})s")
    print(f"4. 起爆时间（弹1/2/3）：{det1:.2f}s | {det2:.2f}s | {det3:.2f}s")
    print(f"5. 有效时间段（合并后）：{merged_interval_str}")

    # 5. 日志文件记录（完整参数，便于复盘）
    logger.info(f"===== 第{current_gen}代最优参数 =====")
    logger.info(
        f"基础信息：总有效遮蔽时长={total_duration:.2f}s | 迭代次数={current_gen} | 适应度值={current_fitness:.2f}")
    logger.info(
        f"无人机参数：飞行速度={v:.2f}m/s（70~140m/s） | 航向={theta_rad:.4f}rad（{theta_deg:.2f}°） | 飞行高度={drone_flight_height:.2f}m")
    logger.info(
        f"第1枚弹：投放延迟={t1:.2f}s | 起爆延迟={d1:.2f}s | 起爆时间={det1:.2f}s | 投放点=({drop1[0]},{drop1[1]},{drop1[2]})m | 起爆点=({expl1[0]},{expl1[1]},{expl1[2]})m | 有效时间段={interval1_str}")
    logger.info(
        f"第2枚弹：投放延迟={t2:.2f}s | 起爆延迟={d2:.2f}s | 起爆时间={det2:.2f}s | 投放点=({drop2[0]},{drop2[1]},{drop2[2]})m | 起爆点=({expl2[0]},{expl2[1]},{expl2[2]})m | 有效时间段={interval2_str}")
    logger.info(
        f"第3枚弹：投放延迟={t3:.2f}s | 起爆延迟={d3:.2f}s | 起爆时间={det3:.2f}s | 投放点=({drop3[0]},{drop3[1]},{drop3[2]})m | 起爆点=({expl3[0]},{expl3[1]},{expl3[2]})m | 有效时间段={interval3_str}")
    logger.info(
        f"约束验证：速度约束={speed_ok} | 投放间隔1={interval1_ok} | 投放间隔2={interval2_ok} | 起爆时间约束={det_time_ok} | 起爆高度约束={expl_z_ok}")
    logger.info(f"合并后有效信息：合并时间段={merged_interval_str} | 合并总时长={total_duration:.2f}s")
    logger.info("=" * 50)


# 6. 主函数（无核心修改，确保A题🔶3-11要求）
if __name__ == "__main__":
    # 6.1 遗传算法参数配置
    gene_space = [
        {'low': drone_speed_min, 'high': drone_speed_max},  # 0: 速度v
        {'low': 0, 'high': 2 * math.pi},  # 1: 航向theta
        {'low': 0, 'high': 50},  # 2: t1（≤50s，避免超导弹时间）
        {'low': 0, 'high': 50},  # 3: t2
        {'low': 0, 'high': 50},  # 4: t3
        {'low': 0.1, 'high': 20},  # 5: d1（≤20s，避免下落过多）
        {'low': 0.1, 'high': 20},  # 6: d2
        {'low': 0.1, 'high': 20}  # 7: d3
    ]

    # 6.2 初始化遗传算法
    ga = pygad.GA(
        num_generations=200,  # 迭代次数
        num_parents_mating=25,  # 父代数量
        fitness_func=fitness_function,
        sol_per_pop=150,  # 种群规模
        num_genes=8,
        gene_space=gene_space,
        parent_selection_type="sss",
        crossover_type="single_point",
        mutation_type="random",
        mutation_percent_genes=5,  # 变异率
        mutation_num_genes=1,
        parallel_processing=8,
        on_generation=on_generation_callback  # 绑定回调函数
    )

    # 6.3 启动优化（日志记录启动信息）
    logger.info("=" * 60)
    logger.info("无人机FY1投放3枚烟幕弹干扰M1策略优化（基于A题.pdf）")
    logger.info(
        f"A题约束参数：导弹到达时间={missile_time_to_fake:.2f}s | 投放间隔≥{smoke_drop_interval_min}s | 起爆高度≥{smoke_min_explode_z}m | 烟幕有效时长={smoke_valid_duration}s")
    logger.info("=" * 60)
    print("=" * 60)
    print("无人机FY1投放3枚烟幕弹干扰M1策略优化（基于A题.pdf）")
    print(f"优化启动时间：{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
    print(f"日志文件已生成：{[h.baseFilename for h in logger.handlers if isinstance(h, logging.FileHandler)][0]}")
    print("=" * 60)
    ga.run()

    # 6.4 解析最终最优结果（与之前一致，确保A题🔶3-11要求）
    best_solution, best_fitness, best_idx = ga.best_solution()
    v_opt = best_solution[0]
    theta_opt_rad = best_solution[1]
    theta_opt_deg = (theta_opt_rad * 180 / math.pi) % 360
    t1_raw_opt, t2_raw_opt, t3_raw_opt = best_solution[2], best_solution[3], best_solution[4]
    d1_opt, d2_opt, d3_opt = best_solution[5], best_solution[6], best_solution[7]
    t_list_opt = sorted([t1_raw_opt, t2_raw_opt, t3_raw_opt])
    t1_opt, t2_opt, t3_opt = t_list_opt[0], t_list_opt[1], t_list_opt[2]
    det1_opt = t1_opt + d1_opt
    det2_opt = t2_opt + d2_opt
    det3_opt = t3_opt + d3_opt

    drop1_opt = calculate_smoke_drop_point(v_opt, theta_opt_rad, t1_opt)
    expl1_opt = calculate_smoke_explosion_point(v_opt, theta_opt_rad, t1_opt, d1_opt)
    drop2_opt = calculate_smoke_drop_point(v_opt, theta_opt_rad, t2_opt)
    expl2_opt = calculate_smoke_explosion_point(v_opt, theta_opt_rad, t2_opt, d2_opt)
    drop3_opt = calculate_smoke_drop_point(v_opt, theta_opt_rad, t3_opt)
    expl3_opt = calculate_smoke_explosion_point(v_opt, theta_opt_rad, t3_opt, d3_opt)

    total_duration_opt, merged_intervals_opt, single_intervals_opt, _ = calculate_total_effective_duration(
        best_solution)
    fmt_interval = lambda intervals: ";".join([f"({s:.2f},{e:.2f})" for s, e in intervals]) if intervals else "无"
    interval1_str_opt = fmt_interval(single_intervals_opt[0])
    interval2_str_opt = fmt_interval(single_intervals_opt[1])
    interval3_str_opt = fmt_interval(single_intervals_opt[2])
    merged_interval_str_opt = fmt_interval(merged_intervals_opt)

    # 6.5 最终结果输出与日志
    print("\n" + "=" * 60)
    print("【最终最优投放策略（基于A题.pdf）】")
    print(f"1. 总有效遮蔽时长：{total_duration_opt:.2f}s（符合🔶3-8要求）")
    print(f"2. 无人机参数：速度={v_opt:.2f}m/s | 航向={theta_opt_deg:.2f}°（符合🔶3-7）")
    print(
        f"3. 第1枚弹：投放延迟={t1_opt:.2f}s | 起爆延迟={d1_opt:.2f}s | 起爆点=({expl1_opt[0]},{expl1_opt[1]},{expl1_opt[2]})m（符合🔶3-5）")
    print(
        f"4. 第2枚弹：投放延迟={t2_opt:.2f}s | 起爆延迟={d2_opt:.2f}s | 起爆点=({expl2_opt[0]},{expl2_opt[1]},{expl2_opt[2]})m（间隔{t2_opt - t1_opt:.2f}s≥1s，符合🔶3-5）")
    print(
        f"5. 第3枚弹：投放延迟={t3_opt:.2f}s | 起爆延迟={d3_opt:.2f}s | 起爆点=({expl3_opt[0]},{expl3_opt[1]},{expl3_opt[2]})m（间隔{t3_opt - t2_opt:.2f}s≥1s，符合🔶3-5）")
    print("=" * 60)

    logger.info("【最终最优结果（基于A题.pdf）】")
    logger.info(f"总有效遮蔽时长：{total_duration_opt:.2f}s（符合🔶3-8）")
    logger.info(
        f"无人机参数：速度={v_opt:.2f}m/s（70~140m/s，🔶3-7） | 航向={theta_opt_deg:.2f}° | 高度={drone_flight_height:.2f}m（等高度，🔶3-7）")
    logger.info(
        f"第1枚弹：投放延迟={t1_opt:.2f}s | 起爆延迟={d1_opt:.2f}s | 起爆时间={det1_opt:.2f}s | 投放点=({drop1_opt[0]},{drop1_opt[1]},{drop1_opt[2]})m | 起爆点=({expl1_opt[0]},{expl1_opt[1]},{expl1_opt[2]})m | 有效时间段={interval1_str_opt}")
    logger.info(
        f"第2枚弹：投放延迟={t2_opt:.2f}s | 起爆延迟={d2_opt:.2f}s | 起爆时间={det2_opt:.2f}s | 投放点=({drop2_opt[0]},{drop2_opt[1]},{drop2_opt[2]})m | 起爆点=({expl2_opt[0]},{expl2_opt[1]},{expl2_opt[2]})m | 有效时间段={interval2_str_opt} | 与第1枚间隔={t2_opt - t1_opt:.2f}s≥1s（🔶3-5）")
    logger.info(
        f"第3枚弹：投放延迟={t3_opt:.2f}s | 起爆延迟={d3_opt:.2f}s | 起爆时间={det3_opt:.2f}s | 投放点=({drop3_opt[0]},{drop3_opt[1]},{drop3_opt[2]})m | 起爆点=({expl3_opt[0]},{expl3_opt[1]},{expl3_opt[2]})m | 有效时间段={interval3_str_opt} | 与第2枚间隔={t3_opt - t2_opt:.2f}s≥1s（🔶3-5）")
    logger.info(
        f"合并有效信息：合并时间段={merged_interval_str_opt} | 合并总时长={total_duration_opt:.2f}s（遮蔽可不连续，🔶3-8）")
    logger.info("=" * 60)

    # 6.6 结果保存到Excel（🔶3-11要求）
    result_data = {
        "无人机编号": ["FY1", "FY1", "FY1"],
        "烟幕弹序号": [1, 2, 3],
        "飞行速度(m/s)": [round(v_opt, 2)] * 3,
        "飞行航向(°)": [round(theta_opt_deg, 2)] * 3,
        "投放延迟(s)": [round(t1_opt, 2), round(t2_opt, 2), round(t3_opt, 2)],
        "起爆延迟(s)": [round(d1_opt, 2), round(d2_opt, 2), round(d3_opt, 2)],
        "起爆时间(s)": [round(det1_opt, 2), round(det2_opt, 2), round(det3_opt, 2)],
        "投放点X(m)": [drop1_opt[0], drop2_opt[0], drop3_opt[0]],
        "投放点Y(m)": [drop1_opt[1], drop2_opt[1], drop3_opt[1]],
        "投放点Z(m)": [drop1_opt[2], drop2_opt[2], drop3_opt[2]],
        "起爆点X(m)": [expl1_opt[0], expl2_opt[0], expl3_opt[0]],
        "起爆点Y(m)": [expl1_opt[1], expl2_opt[1], expl3_opt[1]],
        "起爆点Z(m)": [expl1_opt[2], expl2_opt[2], expl3_opt[2]],
        "单弹有效时间段": [interval1_str_opt, interval2_str_opt, interval3_str_opt],
        "单弹有效时长(s)": [
            round(sum(e - s for s, e in single_intervals_opt[0]), 2) if single_intervals_opt[0] else 0.0,
            round(sum(e - s for s, e in single_intervals_opt[1]), 2) if single_intervals_opt[1] else 0.0,
            round(sum(e - s for s, e in single_intervals_opt[2]), 2) if single_intervals_opt[2] else 0.0
        ],
        "总有效遮蔽时长(s)": [round(total_duration_opt, 2)] * 3,
        "合并后有效时间段": [merged_interval_str_opt] * 3
    }

    df_result = pd.DataFrame(result_data)
    excel_filename = f"result1_{datetime.now().strftime('%Y%m%d%H%M%S')}.xlsx"
    df_result.to_excel(excel_filename, index=False, engine="openpyxl")
    print(f"\n【结果保存】")
    print(f"最优策略已保存至 '{excel_filename}'（符合A题🔶3-11要求）")
    logger.info(
        f"结果文件保存：Excel文件='{excel_filename}' | 日志文件='{[h.baseFilename for h in logger.handlers if isinstance(h, logging.FileHandler)][0]}'")

    # 6.7 绘制迭代曲线
    plt.figure(figsize=(10, 6))
    plt.plot(range(1, len(generation_best_fitness) + 1), generation_best_fitness,
             linewidth=2.5, marker='o', markersize=4, color='#2E86AB')
    plt.xlabel("遗传算法迭代次数（基于A题.pdf）", fontsize=12, fontweight='bold')
    plt.ylabel("最大有效遮蔽时长（s）", fontsize=12, fontweight='bold')
    plt.title("FY1投放3枚烟幕弹：优化迭代过程（A题第三问）", fontsize=14, fontweight='bold')
    plt.grid(True, alpha=0.3, linestyle='--')
    plt.tight_layout()
    plot_filename = f"FY1_3smoke_optimization_curve_{datetime.now().strftime('%Y%m%d%H%M%S')}.png"
    plt.savefig(plot_filename, dpi=300, bbox_inches='tight')
    plt.show()
    print(f"\n【可视化】")
    print(f"迭代曲线已保存至 '{plot_filename}'")
    logger.info(f"可视化文件保存：迭代曲线='{plot_filename}'")
    logger.info("优化流程全部完成（基于A题.pdf）")
    logger.info("=" * 60)